
#include "functions.h"
#include "../ql/qlreader.h"


typedef struct PrivateLoaderData
{
    FILE *              stream;
    ReaderContext *     reader;

    EvalErrorCallback   prev_err_cb;
    void *              prev_tag;
    ReaderContext *     prev_reader;
    RuleEngineContext * re_ctx;
} PrivateLoaderData;

static void free_private_loader_data( PrivateLoaderData * pd )
{
    if( 0 != pd->stream && stdin != pd->stream )
    {
        fclose( pd->stream );
    }

    if( 0 != pd->reader )
    {
        free_reader( pd->reader );
    }

    pd->re_ctx->current_reader = pd->prev_reader;

    ql_free( pd );
}

static void load_function_error_callback( EvalContext * eval )
{
    PrivateLoaderData * pd = get_evaluator_tag( eval );
    void *              prev_tag = pd->prev_tag;
    EvalErrorCallback   prev_error_cb = pd->prev_err_cb;

    // restore previous tag/callback
    set_evaluator_tag( eval, prev_tag );
    set_evaluator_callback( eval, prev_error_cb );

    free_private_loader_data( pd );

    // since our resources are freed, pass error to the previous callback function
    evaluator_emit_error( eval, get_evaluator_last_error(eval) );
}

static Obj *
perform_loading( RuleEngineContext * r, const char * file_name, bool verbose )
{
    PrivateLoaderData * pd;
    AllocContext *      allocator = r->alloc_ctx;
    FILE *              verbose_stream = stderr;
    Obj *               result = g_nil_obj;

    pd = ql_mallocz( allocator, sizeof(PrivateLoaderData) );

    pd->re_ctx = r;
    pd->prev_tag = set_evaluator_tag( r->eval, pd );
    pd->prev_err_cb = set_evaluator_callback( r->eval, load_function_error_callback );
    pd->prev_reader = pd->re_ctx->current_reader;

    /* get input stream */
    if( 0 != file_name )
    {
        pd->stream = fopen( file_name, "rb" );

        if( !pd->stream )
        {
            evaluator_emit_error( r->eval, QL_ERR_FAIL );
        }
    }
    else
    {
        pd->stream = stdin;
    }

    pd->reader = create_reader( allocator, pd->stream, file_name );
    pd->re_ctx->current_reader = pd->reader;

    set_default_namespace( pd->reader, pd->re_ctx->default_ns );

    /* REPL loop */
    for( ;; )
    {
        // show welcome prompt in case of std input
        if( stdin == pd->stream )
        {
            fputs( "\n> ", stderr );
        }

        result = read_obj( pd->reader );
        if( !result )
        {
            ErrResult   reader_err = reader_get_last_error( pd->reader );
            if( reader_err != QL_OK )
            {
                evaluator_emit_error( r->eval, reader_err );
            }

            break;
        }

        if( 0 != verbose && stdin != pd->stream )
        {
            print_obj( result, verbose_stream );
            fputs( " -> ", verbose_stream );
        }

        result = internal_evaluate( r->eval, result );
        if( 0 != result && 0 != verbose )
        {
            print_obj( result, verbose_stream );
            fputs( "\n", verbose_stream );
        }
    }

    // restore previous tag/callback
    set_evaluator_tag( r->eval, pd->prev_tag );
    set_evaluator_callback( r->eval, pd->prev_err_cb );

    // dispose private loader data
    free_private_loader_data( pd );
    return result;
}

Obj *
f_load( void * tag, List * args )
{
    RuleEngineContext * r = tag;
    AllocContext *      allocator = r->alloc_ctx;
    const char *        file_name = 0;
    bool                verbose = false;
    size_t              args_count = list_length( args );

    if( args_count == 1 )
    {
        if( args->car->type != OBJ_STRING )
        {
            evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
        }

        file_name = ((Str *)args->car)->str;
    }
    else if( args_count > 1 )
    {
        /* this is to support multiple arguments
         * expected: name after :name, optional non-nil argument after :verbose
         */
        Id *            kw_name = alloc_id( allocator, "name", 0 );
        Id *            kw_verbose = alloc_id( allocator, "verbose", 0 );
        
        Obj *           f_name = get_list_elem_by_keyword( args, kw_name );
        Obj *           f_verbose = get_list_elem_by_keyword( args, kw_verbose );

        if( 0 == f_name )
        {
            f_name = args->car;
        }

        /* file name */
        if( f_name->type == OBJ_STRING )
        {
            file_name = ((Str *)f_name)->str;
        }
        else if( 0 == objcmp((Obj *)f_name, (Obj *)alloc_id(allocator, "stdin", 0)) )
        {
            file_name = 0;
        }
        else
        {
            evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
        }

        /* verbose flag */
        if( 0 != f_verbose && f_verbose->type != OBJ_NIL )
        {
            verbose = true;
        }
    }
    
    return perform_loading( r, file_name, verbose );
}

